/**********************************************************************************************************
* Author : FUNIOT.XYZ && IOT趣制作
* URL    : https://gitee.com/funiot/esp01s-http-download-file
* Info   : 使用ESP01S直接下载HTTP文件到Flash中
* Data   : 2023/12/19
* Version: 0.01
**********************************************************************************************************/
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Wire.h>  // 使用Wire库，需要包含头文件
#include <FS.h>

//连接指定WIFI
const char* ssid = "funiot";          // 连接WiFi
const char* password = "funiot.xyz";  // 连接WiFi密码

//下载文件的地址：www.funiot.xyz/upload/test.bin
const char* host = "www.funiot.xyz";  // 将要连接的服务器地址
const int httpPort = 80;              // 将要连接的服务器端口
const char* reqRes = "/upload/test.bin";

//保存文件到Flash中，路径：[ESP01S-FLASH]/download/test.bin
String FILE_NAME = "/download/test.bin";  
uint32_t filesize = 0;
union Data32bto4B {
  uint32_t data32;
  uint8_t data_4byte[4];
};

typedef struct Head {
  String name;
  String value;
};
Head head[20];
uint16_t Content_Length = 0;

void setup() {
  Serial.begin(115200);
  Connect_WiFi();
  SPIFFS.begin();
}

void loop() {
  //1.示例1：获取文件大小
  HEAD_Content_Length();
  //2.示例2：下载并保存文件
  bool download_res=Download_bin();
  if(download_res==false)
    Serial.println("Download Fail!");
  else
   Serial.println("Download Success!");
  //3.从Flash中读取文件
  if (SPIFFS.exists(FILE_NAME)) {
    File dataFile;
    //建立File对象用于从SPIFFS中读取文件
    dataFile = SPIFFS.open(FILE_NAME, "r");
    uint32_t filesize = dataFile.size();
    dataFile.close();
    Data32bto4B db324;++
    db324.data32 = filesize;//文件大小
    Serial.print("filesize:");
    Serial.println(filesize);
    SPIFFS_readFile_u8(FILE_NAME);//读取下载的文件
    SPIFFS.remove(FILE_NAME);//删除下载的文件
  }
  delay(2000);
}

/*
*********************************************************************************************************
* 函 数 名: Connect_WiFi()
* 功能说明: 连接wifi
* 形    参：无
* 返 回 值: 无
*********************************************************************************************************
*/
void Connect_WiFi() {
  uint8_t wait_connect = 10;    //尝试次数
  WiFi.begin(ssid, password);  // 启动网络连接
  Serial.print("\r\nWIFI Connection.");
  while (WiFi.status() != WL_CONNECTED) 
  {
    Serial.print(".");
    delay(1000);
    wait_connect--;
    if (wait_connect == 0)
      break;
  }
  if (wait_connect == 0) 
  {
    Serial.println("\r\nWIFI Connection Failed!");
  } 
  else 
  {
    int i = 0;
    uint8_t ipaddress[] = { 0, 0, 0, 0 };
    String ipaddress_str = WiFi.localIP().toString();
    Serial.println("\r\nWIFI Connection SUCCESS: Ip Address"+ipaddress_str);
  }
}

/*
*********************************************************************************************************
* 函 数 名: Download_bin()
* 功能说明: 从指定链接中下载bin文件
* 形 参：无
* 返 回 值: bool:成功 true/失败 false 
*********************************************************************************************************
*/
bool Download_bin() {
  WiFiClient client;
  // 建立http请求信息
  String httpRequest = String("GET ") + reqRes + " HTTP/1.1\r\n" + "Host: " + host + "\r\n\r\n";
  // 尝试连接服务器
  if (client.connect(host, httpPort)) {

    // 向服务器发送http请求信息
    client.print(httpRequest);
    // 获取并显示服务器响应状态行
    String status_response = client.readStringUntil('\n');
    // 使用find跳过HTTP响应头
    if (client.find("\r\n\r\n")) {
      //Serial.println("Found Header End. Start Parsing.");
    }
    const String& payload = client.readString();
    //保存bin文件
    SPIFFS_writeFile_str(FILE_NAME, payload);
    //SPIFFS_readFile_u8(FILE_NAME);
    if (SPIFFS.exists(FILE_NAME)) {
      return true;
    } else {
      return false;
    }
  } else {
    return false;
  }
  client.stop();
}
/*
*********************************************************************************************************
* 函 数 名: HEAD_Content_Length()
* 功能说明: 从指定链接中读取下载文件的大小
* 形    参：无
* 返 回 值: 无
*********************************************************************************************************
*/
void HEAD_Content_Length() {
  WiFiClient client;
  // 建立http请求信息
  String httpRequest = String("HEAD ") + reqRes + " HTTP/1.1\r\n" + "Host: " + host + "\r\n\r\n";
  // 尝试连接服务器
  if (client.connect(host, httpPort)) {

    // 向服务器发送http请求信息
    client.print(httpRequest);
    Serial.print("\r\nSending request: ");
    Serial.println(httpRequest);
    //读取响应数据
    String Value = client.readString();
    //Serial.println("RECV:" + Value);
    //解析响应数据
    Analysis_data_to_Content_Length(Value);
    Content_Length = head[4].value.toInt();
    Serial.print("Get Head Success, Analysis Content_Length:");
    Serial.println(Content_Length);
  } else {
    Serial.println("Get Head failed!");
  }
  client.stop();
}

/*
*********************************************************************************************************
* 函 数 名: Analysis_data_to_Content_Length()
* 功能说明: 从指定链接中提取请求头数据
* 形    参：无
* 返 回 值: 无
*********************************************************************************************************
*/
void Analysis_data_to_Content_Length(String Value) {

  int i = 0, j = 0, k = 0, h = 0, num = 0;
  //获取数据的行数
  for (i = 0; i < Value.length(); i++) {
    char a = Value.charAt(i);
    if (a == '\n')
      num = num + 1;
  }
  //Serial.print("Data rn=" + num);
  //按行解析
  for (h = 0; h < num - 1; h++)  //最后含\r\n空行，不解析
  {
    //Serial.println(h);
    if (h == 0) {
      //解析状态码：HTTP/1.1 200 OK
      k = Value.indexOf('\n');
      head[h].name = "Status";
      head[h].value = Value.substring(0, k - 1);  //去掉\r: HTTP/1.1 200 OK
    } else {
      i = Value.indexOf(':', k + 1);                  //从第二行开始查找冒号
      j = Value.indexOf('\n', i + 1);                 //跳过空格查找第二行末
      head[h].name = Value.substring(k + 1, i);       //从第二行开始截取到冒号处
      head[h].value = Value.substring(i + 2, j - 1);  //跳过冒号跳过空格截取第二行行末
      k = j;
    }
    //Serial.println("head.name=" + head[h].name);
    //Serial.println("head.value=" + head[h].value);
  }
}

/*
*********************************************************************************************************
* 函 数 名: SPIFFS_writeFile_str()
* 功能说明: 写文件
* 形    参：无
* 返 回 值: 无
*********************************************************************************************************
*/
void SPIFFS_writeFile_str(String FILE_NAME, String content) {
  File dataFile = SPIFFS.open(FILE_NAME, "w");  // 建立File对象用于向SPIFFS中的file对象（即/notes.txt）写入信息
  dataFile.println(content);                    // 向dataFile写入字符串信息
  dataFile.close();                             // 完成文件写入后关闭文件
}
/*
*********************************************************************************************************
* 函 数 名: SPIFFS_readFile_u8(String FILE_NAME)
* 功能说明: 读文件
* 形    参：String FILE_NAME 文件名
* 返 回 值: 无
*********************************************************************************************************
*/
void SPIFFS_readFile_u8(String FILE_NAME) {
  //检查是否有FILE_NAME文件
  File dataFile;
  if (SPIFFS.exists(FILE_NAME)) {
    //建立File对象用于从SPIFFS中读取文件
    dataFile = SPIFFS.open(FILE_NAME, "r");
    //读取文件内容并且通过串口监视器输出文件信息
    Serial.print("read:");
    for (int i = 0; i < dataFile.size(); i++) {
      Serial.print(dataFile.read(), HEX);
      Serial.print(" ");
    }
  } else {
    Serial.print(FILE_NAME);
    Serial.print(" NOT FOUND.");
  }
  //完成文件读取后关闭文件
  dataFile.close();
}